// $Id: ids.c 246 2013-10-21 05:07:48Z alan.watson.f $

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <time.h>
#include <math.h>

#include "oaxaca-config.h"
#include "oaxaca.h"
#include "array.h"
#include "ids.h"

#include <sys/time.h>
#include <unistd.h>

#include <ueye.h>

static HIDS camera;

void
ueye_error(const char *activity)
{
  INT code;
  IS_CHAR *text;
  if (is_GetError(camera, &code, &text) != IS_SUCCESS)
    oaxaca_error("ueye error while %s.", activity);
  else
    oaxaca_error("ueye error while %s: %s.", activity, text);
}

static long long
getid(void)
{
  CAMINFO caminfo;	
  int i = is_GetCameraInfo(camera, &caminfo);
  if (i != IS_SUCCESS)
    ueye_error("getting id");
  return caminfo.Select;
}

static long long
getserialnumber(void)
{
  CAMINFO caminfo;	
  int i = is_GetCameraInfo(camera, &caminfo);
  if (i != IS_SUCCESS)
    ueye_error("getting serial number");
  return atoll(caminfo.SerNo);
}

static double
getpixelfrequency(void)
{
  oaxaca_info("getting pixel frequency.");
  UINT u; 
  if (is_PixelClock(camera, IS_PIXELCLOCK_CMD_GET, &u, sizeof(u)) != IS_SUCCESS)
    ueye_error("getting pixel frequency");
  oaxaca_info("pixel frequency is %u MHz.", u);
  return u * 1e6;
}

static double
setpixelfrequency(double frequency)
{
  oaxaca_info("setting pixel frequency.");
  UINT u = round(frequency / 1e6);
  if (is_PixelClock(camera, IS_PIXELCLOCK_CMD_SET, &u, sizeof(u)) != IS_SUCCESS)
    ueye_error("setting pixel frequency");
  return getpixelfrequency();
}

static void
setframefrequency(double frequency)
{
  oaxaca_info("setting frame frequency to %.0f Hz.", frequency);
  double x;
  if (is_SetFrameRate(camera, frequency, &x) != IS_SUCCESS)
    ueye_error("setting frame frequency");
  oaxaca_info("frame frequency is %.0f Hz.", x);
  x = 0;
  if (is_Exposure(camera, IS_EXPOSURE_CMD_SET_EXPOSURE, &x, sizeof(x)) != IS_SUCCESS)
    ueye_error("setting frame exposure time");
  if (is_Exposure(camera, IS_EXPOSURE_CMD_GET_EXPOSURE, &x, sizeof(x)) != IS_SUCCESS)
    ueye_error("getting frame exposure time");
  oaxaca_info("frame exposure time is %.6f s.", x * 1e-3);
}

static double
getframefrequency(void)
{
  oaxaca_info("getting frame frequency.");
  double x;
  if (is_SetFrameRate(camera, IS_GET_FRAMERATE, &x) != IS_SUCCESS)
    ueye_error("getting frame frequency");
  oaxaca_info("frame frequency is %.0f Hz.", x);
	return x;
}

static double
getminframefrequency(void)
{
  oaxaca_info("getting minimum frame frequency.");
  double x0, x1, x2;
  if (is_GetFrameTimeRange(camera, &x0, &x1, &x2) != IS_SUCCESS)
    ueye_error("getting frame time range");
  oaxaca_info("minimum frame time is %.6f s.", x0);
  oaxaca_info("maximum frame time is %.6f s.", x1);
  oaxaca_info("frame time increment is %.6f s.", x2);
  oaxaca_info("minimum frame frequency is %.0f Hz.", 1.0 / x1);
  oaxaca_info("maximum frame frequency is %.0f Hz.", 1.0 / x0);
  return 1.0 / x1;
}

static double
getmaxframefrequency(void)
{
  oaxaca_info("getting maximum frame frequency.");
  double x0, x1, x2;
  if (is_GetFrameTimeRange(camera, &x0, &x1, &x2) != IS_SUCCESS)
    ueye_error("getting frame time range");
  oaxaca_info("minimum frame time is %.6f s.", x0);
  oaxaca_info("maximum frame time is %.6f s.", x1);
  oaxaca_info("frame time increment is %.6f s.", x2);
  oaxaca_info("minimum frame frequency is %.0f Hz.", 1.0 / x1);
  oaxaca_info("maximum frame frequency is %.0f Hz.", 1.0 / x0);
  return 1.0 / x0;
}

static void
setwindow(oaxaca_window_t window)
{
  oaxaca_info("setting window.");
  oaxaca_info("window origin is (%d,%d).", (int) window.sx, (int) window.sy);
  oaxaca_info("window size is (%d,%d).", (int) window.nx, (int) window.ny);
  IS_RECT r;
  r.s32X      = window.sx;
  r.s32Y      = window.sy;
  r.s32Width  = window.nx;
  r.s32Height = window.ny;
  if (is_AOI(camera, IS_AOI_IMAGE_SET_AOI, &r, sizeof(r)) != IS_SUCCESS)
    ueye_error("setting window");
  setframefrequency(getmaxframefrequency());
  getframefrequency();
}

static oaxaca_window_t
getwindow(void)
{
  oaxaca_info("getting window.");
  IS_RECT r;
  if (is_AOI(camera, IS_AOI_IMAGE_GET_AOI, &r, sizeof(r)) != IS_SUCCESS)
    ueye_error("getting window");
  oaxaca_window_t window;
  window.sx = r.s32X;
  window.sy = r.s32Y;
  window.nx = r.s32Width;
	window.ny = r.s32Height;
  oaxaca_info("window origin is (%d,%d).", (int) window.sx, (int) window.sy);
  oaxaca_info("window size is (%d,%d).", (int) window.nx, (int) window.ny);
  return window;
}

oaxaca_detector_t
oaxaca_ids_initialize(int id)
{
  camera = id;

  oaxaca_info("waiting to open detector.");
  sleep(10);

  oaxaca_info("opening detector with id %d.", id);
  int i = is_InitCamera(&camera, NULL);
  if (i == IS_STARTER_FW_UPLOAD_NEEDED) {
    oaxaca_info("uploading firmware.");
    camera = id | IS_ALLOW_STARTER_FW_UPLOAD;
    i = is_InitCamera(&camera, NULL);;
  }
  if (i != IS_SUCCESS)
    ueye_error("opening detector");
  oaxaca_info("id is %d.", getid());
  if (id != getid())
    oaxaca_error("id does not match.");
  oaxaca_info("serial number is %lld.", getserialnumber());

  oaxaca_info("checking display mode.");
  int dm = is_SetDisplayMode(camera, IS_GET_DISPLAY_MODE);
  if (dm != IS_SET_DM_DIB)
    oaxaca_error("display mode is not IS_SET_DM_DIB.");
  // We read the detectors in 12-bit raw mode. The detectors have 10-bit
  // ADCs (range 0x0 to 0x03ff). In 12-bit raw mode, the software pixel
  // values are the hardware pixel values shifted right by 2 bits (range
  // 0x0 to 0xffc). The advantage of this mode is that the full range
  // fits in a 16-bit signed short without truncation and with a
  // precision of 0.25 hardware bits.
  oaxaca_info("setting color mode to 12-bit raw.");
  if (is_SetColorMode(camera, IS_CM_SENSOR_RAW12) != IS_SUCCESS)
    ueye_error("setting color mode");  
  oaxaca_info("setting gain boost to on.");
  if (is_SetGainBoost(camera, IS_SET_GAINBOOST_ON) != IS_SUCCESS)
    ueye_error("setting gain boost");  
  oaxaca_info("setting hardware gain to 0.");
  if (is_SetHardwareGain(camera, 0, 0, 0, 0) != IS_SUCCESS)
    ueye_error("setting hardware gain");
  oaxaca_info("setting automatic black-level mode to on.");
  INT blacklevelmode = IS_AUTO_BLACKLEVEL_ON;
  if (is_Blacklevel(camera, IS_BLACKLEVEL_CMD_SET_MODE, &blacklevelmode, sizeof(blacklevelmode)) != IS_SUCCESS)
    ueye_error("setting black-level mode");

  oaxaca_detector_t detector = {
    .initialized             = 0,
    .abort                   = 0,
    .pixelfrequency          = 48e6,
    .window.sx               = 0,
    .window.sy               = 0,
    .window.nx               = 1280,
    .window.ny               = 1024,
    .framefrequency          = 0.0,
    .frametime               = 0.0,
  };
  detector = oaxaca_ids_configure(detector);

  oaxaca_info("finished initializing.");
  return detector;
}

oaxaca_detector_t
oaxaca_ids_configure(oaxaca_detector_t detector)
{
  oaxaca_info("configuring.");

  setpixelfrequency(detector.pixelfrequency);
  detector.pixelfrequency = getpixelfrequency();
  
  setwindow(detector.window);
  detector.window = getwindow();
     
  detector.minframefrequency = getminframefrequency();
  detector.maxframefrequency = getmaxframefrequency();

  double framefrequency = detector.framefrequency;
  if (framefrequency < detector.minframefrequency)
    framefrequency = detector.minframefrequency;
  if (framefrequency > detector.maxframefrequency)
    framefrequency = detector.maxframefrequency;
  setframefrequency(framefrequency);
  detector.framefrequency = getframefrequency();
  detector.frametime = 1.0 / detector.framefrequency;

  detector.frame                   = 0,
  detector.framesperintegration    = 0,
  detector.integrationtime         = 0.0,
  detector.integration             = 0,
  detector.integrationsperexposure = 0,
  detector.exposuretime            = 0.0,

  oaxaca_info("finished configuring.");
  
  return detector;
}

void
oaxaca_ids_run(int index, int (*frame_handler)(int, double, array_short_t *))
{  
  oaxaca_window_t window = getwindow();
  int nx = window.nx;
  int ny = window.ny;

  int nbuffers = 16;
  char *membuf[nbuffers];
  INT memid[nbuffers];

  oaxaca_info("preparing to acquire frames.");

  for (int i = 0; i < nbuffers; ++i) {
    if (is_AllocImageMem(camera, nx, ny, 16, &membuf[i], &memid[i]) != IS_SUCCESS)
      ueye_error("allocating image memory");
    if (is_AddToSequence(camera, membuf[i], memid[i]) != IS_SUCCESS)
      ueye_error("adding image memory to sequence");
  }

  INT lineincrement;
  if (is_GetImageMemPitch(camera, &lineincrement) != IS_SUCCESS)
      ueye_error("determining line increment"); 

  array_short_t *frame[nbuffers];
  for (int i = 0; i < nbuffers; ++i)
    frame[i] = wrap_array_short(ny, nx, (short *) membuf[i], 
                                lineincrement / sizeof(short));

  if (is_CaptureVideo(camera, IS_WAIT) != IS_SUCCESS)
    ueye_error("acquiring frames");
  is_EnableEvent(camera, IS_SET_EVENT_FRAME);
  
  oaxaca_info("acquiring frames.");
  for (;;) {
    if (is_WaitEvent(camera, IS_SET_EVENT_FRAME, 1000) == IS_SUCCESS) {
      char *lastmembuf;
      if (is_GetActSeqBuf(camera, NULL, NULL, &lastmembuf) != IS_SUCCESS)
         ueye_error("getting image memory");
      int i;
      for (i = 0; i < nbuffers; ++i) {
        if (lastmembuf == membuf[i])
          break;
      }
      if (i == nbuffers)
        oaxaca_error("unexpected last memory buffer pointer.");
      double actualframefrequency;
      if (is_GetFramesPerSecond(camera, &actualframefrequency) != IS_SUCCESS)
        actualframefrequency = 0.0;
      if (frame_handler(index, actualframefrequency, frame[i]) == 0)
        break;
    }
  }
  oaxaca_info("finished acquiring frames.");
  
  is_DisableEvent(camera, IS_SET_EVENT_FRAME);  
  if (is_StopLiveVideo(camera, IS_WAIT) != IS_SUCCESS)
    ueye_error("stopping acquiring frames");
    
  for (int i = 0; i < nbuffers; ++i)
    free_array_short(frame[i]);
  for (int i = 0; i < nbuffers; ++i)
    if (is_FreeImageMem(camera, membuf[i], memid[i]) != IS_SUCCESS)
      ueye_error("while freeing image memory");
}
